using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Threading;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;
using System.Windows.Threading;

using ViewOnCore;
using ViewOnCore.Helper.WPF;
using ViewOnCore.Helper.Collection;
using ViewOnCore.HostContract;
using ViewOnCore.HostContract.Settings;
using ViewOnCore.HostContract.Media;
using ViewOnCore.Device;
using ViewOnCore.Visual;
using VideoPlayerAddIn.Properties;

namespace VideoPlayerAddIn
{
    public partial class VideoPlayer : VisualPart
    {

        #region enum : VideoSize

        public enum VideoSize
        {
            FitSize,
            Double,
            FullScreen
        }

        #endregion

        #region Variables

        private TimeSpan _lastPosition = TimeSpan.MinValue; // Used to handle a bug in MediaElement

        //---- Inactivity management
        static private int _inactivityPeriod = 6000;

        private readonly System.Threading.Timer _mouseTimer;
        private bool _isInactive = false;
        private bool _isInactivityOn = false;

        //----
        private VideoSize _videoSize = VideoSize.FitSize;

        //----
        private Timer _timer;

        private bool _isInTimerCallback = false;

        #endregion

        #region Constructor

        public VideoPlayer()
        {
            InitializeComponent();

            MediaTypeTags.Add("Video");

            //---- Inactivity management
            _mouseTimer = new System.Threading.Timer(new System.Threading.TimerCallback(InactivityCheck), null, _inactivityPeriod, -1);
            MouseMove += new MouseEventHandler(VideoWindow_MouseMove);

            //----
            btnFullScreen.IsEnabled = false;
            btnPrevious.IsEnabled = false;
            btnPlay.IsEnabled = false;
            sliPosition.IsEnabled = false;
            sliVolume.IsEnabled = false;
            btnMute.IsEnabled = false;
            txtProgress.IsEnabled = false;
            mediaElement.IsEnabled = false;

            //----
            sbOpenVideo.ContentClick += new RoutedEventHandler(sbOpenVideo_ContentClick);
            btnPlay.Click += new RoutedEventHandler(btnPlay_Click);
            btnMute.Click += new RoutedEventHandler(btnMute_Click);
            btnPrevious.Click += new RoutedEventHandler(btnPrevious_Click);
            btnFullScreen.Click += new RoutedEventHandler(btnFullScreen_Click);
            sliPosition.ValueChanged += new RoutedPropertyChangedEventHandler<double>(sliPosition_ValueChanged);
            sliVolume.ValueChanged += new RoutedPropertyChangedEventHandler<double>(sliVolume_ValueChanged);

            this.MouseDoubleClick += new MouseButtonEventHandler(VideoWindow_MouseDoubleClick);

            ContextMenu menu = (ContextMenu)FindResource("ContextMenu");
            ((MenuItem)menu.Items[0]).Click += new RoutedEventHandler(contextMenuOpenFile_Click);
            ((MenuItem)menu.Items[1]).Click += new RoutedEventHandler(contextMenuOpenURL_Click);
            ((MenuItem)menu.Items[3]).Click += new RoutedEventHandler(contextMenuPlayPause_Click);
            ((MenuItem)menu.Items[4]).Click += new RoutedEventHandler(contextMenuBackward_Click);
            ((MenuItem)menu.Items[6]).Click += new RoutedEventHandler(contextMenuFullScreen_Click);
            ((MenuItem)menu.Items[7]).Click += new RoutedEventHandler(contextMenuFitSize_Click);
            ((MenuItem)menu.Items[8]).Click += new RoutedEventHandler(contextMenuDoubleSize_Click);

            //---- Drop
            this.AllowDrop = true;
            this.Drop += new DragEventHandler(VideoPlayer_Drop);
            this.DragOver += new DragEventHandler(VideoPlayer_DragOver);

            //----
            mediaElement.MediaEnded += new RoutedEventHandler(mediaElement_MediaEnded);
            mediaElementLayer.PreviewMouseRightButtonDown += new MouseButtonEventHandler(mediaElement_PreviewMouseRightButtonDown);

            //---- Timer
            _timer = new Timer(new TimerCallback(TimerCallback));
            _timer.Change(-1, -1);

            //----
            Host.MediaService.MediaSelectionChanged += new EventHandler(MediaService_MediaSelectionChanged);
            Host.MediaService.PlayerStatusChanged += new EventHandler(MediaService_PlayerStatusChanged);
            Host.HostCommand += new HostCommandEventHandler(Host_HostCommand);
        }

        #endregion

        #region mediaElement_MediaFailed

        void mediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
        {
            spinner.Visibility = Visibility.Collapsed;
            spinner.Stop();

            txtError.Text = GetErrorMessage(e.ErrorException.Message);

            txtError.Visibility = Visibility.Visible;
            mediaElement.Stop();
        }

        #endregion

        #region mediaElement_MediaOpened

        void mediaElement_MediaOpened(object sender, RoutedEventArgs e)
        {
            spinner.Visibility = Visibility.Collapsed;
            spinner.Stop();

            if (mediaElement.NaturalVideoWidth < 1)
                return;

            //---- Restore the position
            if (_lastPosition != TimeSpan.MinValue)
            {
                mediaElement.Position = _lastPosition;
                _lastPosition = TimeSpan.MinValue;
                if (Host.MediaService.MediaPlayStatus != MediaPlayStatus.Play)
                    mediaElement.Pause();
            }

            if (Host.MediaService.MediaPlayStatus == MediaPlayStatus.Play)
            {
                _timer.Change(500, 500);

                btnFullScreen.IsEnabled = true;
                btnPrevious.IsEnabled = true;
                btnPlay.IsEnabled = true;
                sliPosition.IsEnabled = true;
                sliVolume.IsEnabled = true;
                btnMute.IsEnabled = true;
                txtProgress.IsEnabled = true;
                mediaElement.IsEnabled = true;
            }
        }

        #endregion

        #region SwitchVideoMode

        private VideoSize _oldVideoSize;
        private WindowState oldWindowState;
        private Grid oldParent = null;
        private int oldParentIndex = -1;
        private object fullScreenContent;

        private void SwitchVideoMode(VideoSize currentMode, VideoSize newMode)
        {
            if (currentMode == newMode)
                return;

            //---- Restore the application visual tree when back from FullScreen
            if (currentMode == VideoSize.FullScreen)
            {
                Host.ApplicationWindow.WindowState = oldWindowState;

                Host.ApplicationWindow.Content = null;
                oldParent.Children.Insert(oldParentIndex, this);
                Host.ApplicationWindow.Content = fullScreenContent;
                viewBox.Stretch = Stretch.None;

                fullScreenContent = null;
                oldParent = null;
                oldParentIndex = -1;
            }

            //---- Fit size
            if (newMode == VideoSize.FitSize)
            {
                mediaElement.Width = mediaElement.NaturalVideoWidth;
                mediaElement.Height = mediaElement.NaturalVideoHeight;
            }

            //---- Double size
            if (newMode == VideoSize.Double)
            {
                mediaElement.Width = mediaElement.NaturalVideoWidth * 2;
                mediaElement.Height = mediaElement.NaturalVideoHeight * 2;
            }

            //---- Full screen
            if (newMode == VideoSize.FullScreen)
            {
                //---- Full screen
                oldWindowState = Host.ApplicationWindow.WindowState;
                fullScreenContent = Host.ApplicationWindow.Content;
                oldParent = (Grid)this.Parent;
                oldParentIndex = oldParent.Children.IndexOf(this);

                Host.ApplicationWindow.WindowState = WindowState.Maximized;
                ((Grid)this.Parent).Children.Remove(this);
                viewBox.Stretch = Stretch.Uniform;
                Host.ApplicationWindow.Content = this;
            }

            //---- This will force to call "mediaElement_MediaOpened" and then reset the position.
            if (currentMode == VideoSize.FullScreen || newMode == VideoSize.FullScreen)
            {
                _lastPosition = mediaElement.Position;
                mediaElement.Stop();
                mediaElement.Position = TimeSpan.Zero;
                mediaElement.Play();
            }

            _videoSize = newMode;
        }

        #endregion

        #region GetErrorMessage

        // http://blogs.msdn.com/joshpoley/pages/errors-00d-facility-mediaserver.aspx
        private string GetErrorMessage(string message)
        {
            if (message.Contains("0xC00D10D1"))
                return Globalization.VideoWindow_NoCodec;

            return Globalization.VideoWindow_UnableToLoadVideo + ":" + message;
        }

        #endregion

        #region VisualPart : Properties

        override public string Handle
        {
            get
            {
                return "VideoPlayer";
            }
        }

        #endregion

        #region VisualPart : Management

        override public VisualPartDisplayInformation OnRequestDisplayInformation(VisualPartDisplayInformation currentDisplayInformation, VisualPartDisplayInformation leavedDisplayInformation)
        {
            return new VisualPartDisplayInformation();
        }

        /// <summary>
        /// Stop the video once this player is not active.
        /// </summary>
        public override void OnPreHide(Control container)
        {
            if (Host.MediaService.MediaPlayStatus == MediaPlayStatus.Play &&
                Host.MediaService.CurrentMedia is Video)
            {
                Host.MediaService.Stop();

                AsyncPlayerStatusChanged();
            }
        }

        override public VisualPartPriority OnRequestVisualPartPriority()
        {
            if (Host.VisualService.VisualPartMode != VisualPartMode.Normal)
                return VisualPartPriority.None;

            if (Host.MediaService.CurrentMediaDeviceFeature.MediaTypeTags.Contains("Video"))
                return VisualPartPriority.Normal;

            return VisualPartPriority.None;
        }

        #endregion

        #region Inactivity management : Auto hide

        /// <summary>
        /// Does we check for inactivity now ?
        /// </summary>
        public bool IsInactivityOn
        {
            get
            {
                return _isInactivityOn;
            }
            set
            {
                _isInactivityOn = value;
                if (!_isInactivityOn)
                    SetMouseActive();
            }
        }

        private delegate void HideMouseHandler(object state);
        private void InactivityCheck(object state)
        {
            if (!base.Dispatcher.CheckAccess())
                base.Dispatcher.Invoke(DispatcherPriority.Send, new HideMouseHandler(InactivityCheck), state);
            else
                SetMouseInactive();
        }

        void VideoWindow_MouseMove(object sender, MouseEventArgs e)
        {
            //double mouseX = e.GetPosition(this).X;
            //double mouseY = e.GetPosition(this).Y;

            //if (mouseY < 0 || mouseY > ActualHeight || mouseX < 0 || mouseX > ActualWidth)
            //{
            //    _mouseTimer.Change(_inactivityPeriod, -1);
            //    return;
            //}

            //if (mouseY > this.ActualHeight - 30 && mouseY < ActualHeight && mouseX > 0 && mouseX < ActualWidth)
            //{
            //    SetMouseActive();
            //    _mouseTimer.Change(-1, -1);
            //    return;
            //}

            //bool isMouseOverBar = false; // (mouseY > this.ActualHeight - 30 && mouseY < ActualHeight && mouseX > 0 && mouseX < ActualWidth);
            //_mouseTimer.Change(isMouseOverBar ? -1 : _inactivityPeriod, -1);

            _mouseTimer.Change(_inactivityPeriod, -1);
            SetMouseActive();
        }

        private void SetMouseInactive()
        {
            if (!_isInactivityOn || _isInactive)
                return;

            double mouseX = Mouse.GetPosition(this).X;
            double mouseY = Mouse.GetPosition(this).Y;
            if (mouseY > ActualHeight - 30 && mouseY < ActualHeight && mouseX > 0 && mouseX < ActualWidth)
                return;

            _isInactive = true;
            //DeactivateScreenSaver = true;
            base.Cursor = Cursors.None;

            Storyboard storyboard = (Storyboard)FindResource("SetMouseInactiveInner");
            storyboard.Begin(this);
        }

        private void SetMouseActive()
        {
            if (!_isInactivityOn || !_isInactive)
                return;

            //DeactivateScreenSaver = false;

            base.Cursor = Cursors.Arrow;

            Storyboard storyboard = (Storyboard)FindResource("SetMouseActiveInner");
            storyboard.Begin(this);

            _isInactive = false;
        }

        #endregion

        #region Contextual Menu

        void mediaElement_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            ContextMenu menu = (ContextMenu)FindResource("ContextMenu");

            if (Host.MediaService.MediaPlayStatus == MediaPlayStatus.Play)
                ((MenuItem)menu.Items[3]).Header = Globalization.VideoPlayer_Pause;
            else
                ((MenuItem)menu.Items[3]).Header = Globalization.VideoPlayer_Play;

            menu.IsOpen = true;
            e.Handled = true;
        }

        void contextMenuOpenFile_Click(object sender, RoutedEventArgs e)
        {
            OpenFile();
        }

        void contextMenuOpenURL_Click(object sender, RoutedEventArgs e)
        {
            OpenUrl();
        }

        void contextMenuPlayPause_Click(object sender, RoutedEventArgs e)
        {
            btnPlay_Click(null, null);
        }

        void contextMenuBackward_Click(object sender, RoutedEventArgs e)
        {
            mediaElement.Position = TimeSpan.Zero;
        }

        void contextMenuFullScreen_Click(object sender, RoutedEventArgs e)
        {
            if (_videoSize == VideoSize.FullScreen)
                return;
            ReverseFullScreen();
        }

        void contextMenuFitSize_Click(object sender, RoutedEventArgs e)
        {
            if (_videoSize == VideoSize.FitSize)
                return;

            SwitchVideoMode(_videoSize, VideoSize.FitSize);
        }

        void contextMenuDoubleSize_Click(object sender, RoutedEventArgs e)
        {
            if (_videoSize == VideoSize.Double)
                return;

            SwitchVideoMode(_videoSize, VideoSize.Double);
        }

        #endregion

        #region OpenFile

        private void OpenFile()
        {
            FileBrowserDialog dialog = new FileBrowserDialog();

            string filter = "Video files|";
            foreach (string ext in Host.MediaService.SupportedVideoExtensions.Values)
                filter += "*" + ext + ";";
            filter += "|All files (*.*)|*.*";

            dialog.Filter = filter; // "Images files|*.gif;*.jpg;*.jpeg;*.png;*.bmp|All files (*.*)|*.*";
            //dialog.InitialDirectory = initialDirectory;
            dialog.Title = "Select an video file";
            dialog.Multiselect = false;
            dialog.CheckFileExists = true;

            if (dialog.ShowDialog() != FileBrowserDialog.DialogResult.OK)
                return;

            Video video = Video.FromFile(dialog.FileName);

            //---- Play
            Host.MediaService.PlayMedia(video);
        }

        #endregion

        #region OpenUrl

        private void OpenUrl()
        {
            QuestionDialog dialog = new QuestionDialog(QuestionDialog.QuestionDialogType.OkCancel, "Please, enter the video URL", "", "http://", null, 350);
            dialog.ShowDialog(Host.ApplicationDesktop,
                                                new ChildWindowDialogClosedHandler(OnConfirmOpenUrl));
        }

        private void OnConfirmOpenUrl(ChildWindowDecorator window)
        {
            //---- Check if it is Ok to delete
            QuestionDialog dialog = window.Parent as QuestionDialog;

            if (!dialog.IsOkYes)
                return;

            //---- Bad URL
            if (dialog.Value.Trim().Length < 1)
            {
                MessageDialog messageDialog = new MessageDialog(Globalization.VideoPart_InvalidVideoURL, "", 350);
                messageDialog.ShowDialog((Desktop)Host.ApplicationDesktop);
                return;
            }

            //---- Play
            Video video = Video.FromUri(dialog.Value.Trim());
            if (video == null)
            {
                MessageDialog messageDialog = new MessageDialog(Globalization.VideoPart_InvalidVideoURL, "", 350);
                messageDialog.ShowDialog((Desktop)Host.ApplicationDesktop);
                return;
            }

            Host.MediaService.PlayMedia(video);
        }

        #endregion

        #region Event : Open video button

        void sbOpenVideo_ContentClick(object sender, RoutedEventArgs e)
        {
            if (sbOpenVideo.SelectedIndex == 0)
                OpenFile();
            else
                OpenUrl();
        }

        #endregion

        #region Play / Pause

        void btnPlay_Click(object sender, RoutedEventArgs e)
        {
            if (Host.MediaService.MediaPlayStatus == MediaPlayStatus.Play)
            {
                Host.MediaService.Stop();
                IsInactivityOn = false;
            }
            else
            {
                Host.MediaService.Play();
                IsInactivityOn = true;
            }
        }

        #endregion

        #region Mute

        void btnMute_Click(object sender, RoutedEventArgs e)
        {
            mediaElement.IsMuted = !mediaElement.IsMuted;

            if (mediaElement.IsMuted)
            {
                patMuteOff.Visibility = Visibility.Visible;
                patMuteOn.Visibility = Visibility.Collapsed;
            }
            else
            {
                patMuteOff.Visibility = Visibility.Collapsed;
                patMuteOn.Visibility = Visibility.Visible;
            }
        }

        #endregion

        #region Position

        void sliPosition_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (_isInTimerCallback)
                return;

            double ticks = mediaElement.NaturalDuration.TimeSpan.Ticks * (sliPosition.Value / sliPosition.Maximum);
            mediaElement.Position = new TimeSpan((long)ticks);
        }

        void btnPrevious_Click(object sender, RoutedEventArgs e)
        {
            sliPosition.Value = 0;
        }

        #endregion

        #region Volume

        void sliVolume_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            mediaElement.Volume = sliVolume.Value / sliVolume.Maximum;
        }

        #endregion

        #region Events : Full screen

        void VideoWindow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ReverseFullScreen();
        }

        void btnFullScreen_Click(object sender, RoutedEventArgs e)
        {
            ReverseFullScreen();
        }

        #endregion

        #region ReverseFullScreen

        private void ReverseFullScreen()
        {
            if (!mediaElement.IsEnabled)
                return;

            //--- Reverse the size
            if (_videoSize != VideoSize.FullScreen)
            {
                griFullScreen.Visibility = Visibility.Collapsed;
                griNormalScreen.Visibility = Visibility.Visible;

                _oldVideoSize = _videoSize;
                SwitchVideoMode(_videoSize, VideoSize.FullScreen);
            }
            else
            {
                griFullScreen.Visibility = Visibility.Visible;
                griNormalScreen.Visibility = Visibility.Collapsed;

                _videoSize = _oldVideoSize;
                SwitchVideoMode(VideoSize.FullScreen, _videoSize);
            }
        }

        #endregion

        #region Timer callback

        private void TimerCallback(object state)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                (DelegateInvoker)delegate { AsyncTimerCallback(); });
        }

        private void AsyncTimerCallback()
        {
            _isInTimerCallback = true;

            try
            {
                sliPosition.Value = sliPosition.Maximum * (((double)mediaElement.Position.Ticks) / mediaElement.NaturalDuration.TimeSpan.Ticks);

                txtProgress.Text = mediaElement.Position.Hours + ":" + mediaElement.Position.Minutes + ":" + mediaElement.Position.Seconds;
                txtProgress.Text += "/" + mediaElement.NaturalDuration.TimeSpan.Hours + ":" + mediaElement.NaturalDuration.TimeSpan.Minutes + ":" + mediaElement.NaturalDuration.TimeSpan.Seconds;
            }
            catch
            {
                // WPF bug
                // "Unable to return a TimeSpan property value for a Duration value of 'Automatic'. Check the HasTimeSpan property before requesting the TimeSpan property value from a Duration."
            }

            _isInTimerCallback = false;
        }

        #endregion

        #region MediaEnded

        void mediaElement_MediaEnded(object sender, RoutedEventArgs e)
        {
            sliPosition.Value = 0;

            Host.MediaService.Stop();
        }

        #endregion

        #region MediaService : PlayerStatusChanged

        void MediaService_PlayerStatusChanged(object sender, EventArgs e)
        {
            Dispatcher.Invoke(DispatcherPriority.Normal,
                (DelegateInvoker)delegate { AsyncPlayerStatusChanged(); });
        }

        private void AsyncPlayerStatusChanged()
        {
            //---- Stop/Pause
            if (Host.MediaService.MediaPlayStatus != MediaPlayStatus.Play)
            {
                _timer.Change(-1, -1);
                mediaElement.Pause();

                patPause.Visibility = Visibility.Collapsed;
                patPlay.Visibility = Visibility.Visible;
                return;
            }

            //---- Play
            if (!IsActive || !VisualPartHelper.IsCurrentMediaDeviceSupported(this.MediaTypeTags))
                return;

            _timer.Change(500, 500);
            mediaElement.Play();

            patPause.Visibility = Visibility.Visible;
            patPlay.Visibility = Visibility.Collapsed;
        }

        #endregion

        #region MediaService : MediaSelectionChanged

        void MediaService_MediaSelectionChanged(object sender, EventArgs e)
        {
            if (!IsActive || !VisualPartHelper.IsCurrentMediaDeviceSupported(this.MediaTypeTags))
                return;

            Dispatcher.Invoke(DispatcherPriority.Normal,
                (DelegateInvoker)delegate { AsyncMediaSelectionChanged(); });
        }

        private void AsyncMediaSelectionChanged()
        {
            mediaElement.Source = new Uri(Host.MediaService.CurrentMedia.Path);
            mediaElement.MediaOpened += new RoutedEventHandler(mediaElement_MediaOpened);
            mediaElement.MediaFailed += new EventHandler<ExceptionRoutedEventArgs>(mediaElement_MediaFailed);

            IsInactivityOn = true;

            sliVolume.Value = sliVolume.Maximum * mediaElement.Volume;
        }

        #endregion

        #region Host : HostCommand

        void Host_HostCommand(object sender, string commandId, params object[] parameters)
        {
            if (Host.VisualService.VisualPartMode != VisualPartMode.Normal)
                return;

            if (commandId == HostCommandIds.PlayMediaData && parameters[0] is Video)
            {
                //---- Activate the right device
                MediaDevice device = Host.MediaService.FindDeviceByHandle("MyLibrary");
                MediaFeature feature = device.FindMediaFeature("Video");

                Host.MediaService.ActivateMediaDevice(device, feature, null);

                //---- Play
                Video video = (Video)parameters[0];
                Host.MediaService.PlayMedia(video);
            }
        }

        #endregion

        #region Drop file on list

        void VideoPlayer_DragOver(object sender, DragEventArgs e)
        {
            //if (e.Data is System.Windows.DataObject && ((System.Windows.DataObject)e.Data).ContainsFileDropList())
            //{
            //    foreach (string filePath in ((System.Windows.DataObject)e.Data).GetFileDropList())
            //    {      // Processing here
            //    }
            //}

            //---- File
            string[] filePathInfo = e.Data.GetData("FileDrop", true) as string[];
            if (filePathInfo == null)
            {
                e.Handled = true;
                e.Effects = DragDropEffects.None;
                return;
            }

            foreach (string filePath in filePathInfo)
                if (Host.MediaService.IsSupportedVideoFile(filePathInfo[0]))
                {
                    e.Effects = DragDropEffects.Copy;
                    return;
                }

            e.Handled = true;
            e.Effects = DragDropEffects.None;
        }

        void VideoPlayer_Drop(object sender, DragEventArgs e)
        {
            //---- File
            string[] filePathInfo = e.Data.GetData("FileDrop", true) as string[];
            if (filePathInfo == null)
                return;

            foreach (string filePath in filePathInfo)
                if (Host.MediaService.IsSupportedVideoFile(filePath))
                {
                    //---- Import the file
                    Video video = Video.FromFile(filePath);
                    if (video != null)
                        Host.MediaService.PlayMedia(video);

                    return;
                }
        }

        #endregion

    }
}